import RNFS, { DownloadBeginCallbackResult } from 'react-native-fs';
import fetch from 'rnx-fetch';
import { flatten, prop, sortBy } from 'ramda';
import moment from 'moment';

import {
  DownloadVideoIndexProps,
  DownloadVideoTask,
  InitDownloadVideoTaskProps,
  TsItem
} from './types';
import {
  VIDEO_TYPE_DESC,
  DOWNLOAD_PATH,
  M3U8_FAIL_LIMIT,
  MOST_TASK_NUM,
  GET_VIDEO_TOKEN_PROD,
  GET_VIDEO_TOKEN_DEV,
  DOWNLOAD_TAG,
  INIT_CAN_PLAY_TS_NUM,
  INIT_CAN_PLAY_PROGRESS
} from './constant';
import { DEBUG } from '../../constant';
import Log from '../../common/log';
import customAwait from '../customAwait';
import rnfsDownload from './rnfsDownload';

const getVideoToken = (url: string): Promise<any> => {
  return fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'audio/x-mpegurl',
      token: 'offline-aikid'
    }
  });
};

const initDownloadVideoTask = (
  props: InitDownloadVideoTaskProps
): DownloadVideoTask => {
  const {
    ignoreTsList: originTsList = [],
    onSuccess,
    onProgress,
    onDelete,
    onStart,
    onPause,
    onFail,
    onCanPlay,
    onInitCanPlay,
    onTsListChanged,
    ...downloadVideoTask
  } = props;
  let failCount = 0; // 下载失败计数器
  const tsDownloadQueue: string[] = []; // ts下载队列
  let ignoreTsList: string[] = [...originTsList]; // 忽略的ts，[00001, 00002...]
  let tsList: TsItem[] = [];

  const getTsIsIgnored = (tsName: string): boolean => {
    // 前两个ts文件必须下完
    if (tsName.includes('00001') || tsName.includes('00002')) {
      return false;
    }
    for (let i = 0; i < ignoreTsList.length; i += 1) {
      if (tsName.includes(ignoreTsList[i])) {
        return true;
      }
    }
    return false;
  };

  const getTaskIsFinished = (): boolean => {
    const { indexHaveDone, keyHaveDone } = downloadVideoTask;

    const tsHaveDone = tsList.every(
      (item): boolean => item.hasDone || item.isIgnore
    );
    const allIndexHaveDone = indexHaveDone.every((item): boolean => item);
    const allKeyHaveDone = keyHaveDone.every((item): boolean => item);

    return allIndexHaveDone && allKeyHaveDone && tsHaveDone && !!tsList.length;
  };

  const updateProgress = (): void => {
    const {
      indexHaveDone,
      keyHaveDone,
      id,
      parentId,
      urls
    } = downloadVideoTask;
    // ts列表为空的情况，此时无法具体统计进度，按1%计算
    if (tsList.length === 0) {
      downloadVideoTask.progress = downloadVideoTask.progress || 0.01;
    } else {
      // 开始下载ts的情况
      const indexAndKeyFinishNum = indexHaveDone.length + keyHaveDone.length;
      const total = tsList.length + indexAndKeyFinishNum;
      const tsFinishedNum = tsList.reduce((finishedNum, current): number => {
        const val = current.hasDone ? 1 : 0;
        return finishedNum + val;
      }, 0);
      downloadVideoTask.progress = Number(
        ((tsFinishedNum + indexAndKeyFinishNum) / total).toFixed(2)
      );
      downloadVideoTask.tsDoneNum = tsFinishedNum;
    }

    if (
      downloadVideoTask.tsDoneNum >= INIT_CAN_PLAY_TS_NUM ||
      downloadVideoTask.progress >= INIT_CAN_PLAY_PROGRESS
    ) {
      onInitCanPlay(downloadVideoTask);
    }

    // 前两个ts下载完触发ON_CAN_PLAY事件
    if (
      (tsList.length === 1 && tsList[0].hasDone) ||
      (tsList.length > 1 && tsList[0].hasDone && tsList[1].hasDone)
    ) {
      onCanPlay(downloadVideoTask);
    }

    // 如果暂停/失败后仍然有个别ts下载完成，则不触发进度回调
    if (!downloadVideoTask.isPaused || !downloadVideoTask.isFailed) {
      onProgress(downloadVideoTask);
    }

    // 整体task下载成功
    if (getTaskIsFinished()) {
      Log.i(
        DOWNLOAD_TAG,
        JSON.stringify({
          event: 'downloadVideoSuccess',
          id,
          parentId,
          urls
        })
      );
      downloadVideoTask.isPaused = true;
      onSuccess(downloadVideoTask);
    }
  };

  // 下载m3u8和key的方法
  const downloadVideoIndex = async (
    downloadVideoIndexProps: DownloadVideoIndexProps
  ): Promise<TsItem[]> => {
    const {
      url,
      index,
      indexHasDone,
      keyHasDone,
      folderName,
      serverDir
    } = downloadVideoIndexProps;
    const { type } = downloadVideoTask;
    const downloadPath = `${DOWNLOAD_PATH}/${folderName}`; // videos/1/c1c102cbf2ae973811c782720d49120e
    const m3u8Path = `${downloadPath}/${VIDEO_TYPE_DESC[type]}.m3u8`; // videos/1/c1c102cbf2ae973811c782720d49120e/sd.m3u8
    const tsItems: TsItem[] = [];
    let keyUrl = '';

    downloadVideoTask.m3u8Paths[index] = m3u8Path;

    const downloadKey = async (): Promise<void> => {
      if (downloadVideoTask.isPaused || downloadVideoTask.isFailed) {
        return;
      }

      const reload = downloadKey;

      try {
        const videoTokenFile = await getVideoToken(
          `${DEBUG ? GET_VIDEO_TOKEN_DEV : GET_VIDEO_TOKEN_PROD}${
            folderName.split('/')[1]
          }.m3u8`
        );

        const videoTokenText = await videoTokenFile.text();

        const stringList = videoTokenText.split('\n');

        const videoToken = stringList[2].split('m3u8?')[1];

        const keyName = `${VIDEO_TYPE_DESC[type]}.key`;
        const keyPath = `${DOWNLOAD_PATH}/${folderName}/${keyName}`;

        const options = {
          fromUrl: `${keyUrl}&${videoToken}`,
          toFile: `${keyPath}`,
          background: true,
          connectionTimeout: 8000,
          readTimeout: 8000,
          begin: (res: DownloadBeginCallbackResult): void => {
            const { jobId } = res;

            if (downloadVideoTask.isPaused || downloadVideoTask.isFailed) {
              RNFS.stopDownload(jobId);
            }
          }
        };

        const ret = await rnfsDownload(options);
        const [error, res] = await customAwait(ret.promise);
        if (error) {
          Log.i(
            DOWNLOAD_TAG,
            JSON.stringify({
              event: 'videoKeyDownloadFail',
              url: `${DEBUG ? GET_VIDEO_TOKEN_DEV : GET_VIDEO_TOKEN_PROD}${
                folderName.split('/')[1]
              }.m3u8`,
              error
            })
          );
          failCount += 1;
          if (failCount === M3U8_FAIL_LIMIT) {
            downloadVideoTask.isFailed = true;
            onFail(downloadVideoTask);
          }
          if (!downloadVideoTask.isPaused && !downloadVideoTask.isFailed) {
            reload();
          }
        }
        if (res) {
          const { statusCode } = res;
          if (statusCode === 200) {
            failCount = 0;
            Log.i(
              DOWNLOAD_TAG,
              JSON.stringify({
                event: 'videoKeyDownloadSuccess',
                url: `${DEBUG ? GET_VIDEO_TOKEN_DEV : GET_VIDEO_TOKEN_PROD}${
                  folderName.split('/')[1]
                }.m3u8`,
                error
              })
            );
          } else {
            Log.i(
              DOWNLOAD_TAG,
              JSON.stringify({
                event: 'videoKeyDownloadFail',
                url: `${DEBUG ? GET_VIDEO_TOKEN_DEV : GET_VIDEO_TOKEN_PROD}${
                  folderName.split('/')[1]
                }.m3u8`,
                error
              })
            );
            failCount += 1;
            if (failCount === M3U8_FAIL_LIMIT) {
              downloadVideoTask.isFailed = true;
              onFail(downloadVideoTask);
            }
            if (!downloadVideoTask.isPaused && !downloadVideoTask.isFailed) {
              reload();
            }
          }
        }

        const m3u8Content = await RNFS.readFile(m3u8Path);
        // Android10 写文件不会覆盖，需要删除重写
        await RNFS.unlink(m3u8Path);
        await RNFS.writeFile(m3u8Path, m3u8Content.replace(keyUrl, keyName));
        // key下载成功 进度更新
        downloadVideoTask.keyHaveDone[index] = true;
        updateProgress();
      } catch (error) {
        Log.i(
          DOWNLOAD_TAG,
          JSON.stringify({
            event: 'videoKeyDownloadFail',
            url: `${DEBUG ? GET_VIDEO_TOKEN_DEV : GET_VIDEO_TOKEN_PROD}${
              folderName.split('/')[1]
            }.m3u8`,
            error
          })
        );
      }
    };

    const readIndex = async (): Promise<void> => {
      const m3u8Content = await RNFS.readFile(m3u8Path);

      const stringList = m3u8Content.split('\n');

      stringList.forEach((item): void => {
        if (item.includes('#EXT-X-KEY')) {
          keyUrl = item
            .split('URI=')[1]
            .replace(/"/g, '')
            .replace('\r', '');
        } else if (item.includes('.ts')) {
          const tsName = item.replace('\r', '');
          // 防止ts重复
          const tsIndex = tsItems.findIndex(
            (ts): boolean => ts.tsName === tsName
          );
          // ts列表还没有形成时即需要跳过部分ts
          if (tsIndex === -1) {
            tsItems.push({
              tsId: `${folderName}/${tsName}`,
              serverDir,
              folderName,
              tsName,
              hasDone: false,
              jobId: 0,
              isIgnore: getTsIsIgnored(tsName)
            });
          }
        }
      });

      // 如果key没有下载完成才去下载key并且是加密视频的情况
      if (!keyHasDone && keyUrl) {
        await downloadKey();
      } else {
        Log.i(
          DOWNLOAD_TAG,
          JSON.stringify({
            event: 'noNeedToDownloadKey'
          })
        );
        downloadVideoTask.keyHaveDone[index] = true;
        updateProgress();
      }
    };

    // 下载m3u8，即index
    const downloadIndex = async (): Promise<void> => {
      if (downloadVideoTask.isPaused || downloadVideoTask.isFailed) {
        return;
      }

      const reload = downloadIndex;

      await RNFS.mkdir(downloadPath);

      const options = {
        fromUrl: url,
        toFile: m3u8Path,
        background: true,
        connectionTimeout: 8000,
        readTimeout: 8000,
        begin: (res: DownloadBeginCallbackResult): void => {
          const { jobId } = res;

          if (downloadVideoTask.isPaused || downloadVideoTask.isFailed) {
            RNFS.stopDownload(jobId);
          }
        }
      };

      const ret = await rnfsDownload(options);
      const [error, res] = await customAwait(ret.promise);
      if (error) {
        Log.i(
          DOWNLOAD_TAG,
          JSON.stringify({
            event: 'm3u8DownloadFailed',
            folderName,
            error
          })
        );
        failCount += 1;
        if (failCount === M3U8_FAIL_LIMIT) {
          downloadVideoTask.isFailed = true;
          onFail(downloadVideoTask);
        }
        if (!downloadVideoTask.isPaused && !downloadVideoTask.isFailed) {
          reload();
        }
        return;
      }
      if (res) {
        const { statusCode } = res;
        if (statusCode === 200) {
          Log.i(
            DOWNLOAD_TAG,
            JSON.stringify({
              event: 'm3u8DownloadSuccess',
              folderName,
              res
            })
          );
          // m3u8下载成功 进度更新
          downloadVideoTask.indexHaveDone[index] = true;
          updateProgress();
          failCount = 0;
          await readIndex();
        } else {
          Log.i(
            DOWNLOAD_TAG,
            JSON.stringify({
              event: 'm3u8DownloadFailed',
              folderName,
              res
            })
          );
          failCount += 1;
          if (failCount === M3U8_FAIL_LIMIT) {
            downloadVideoTask.isFailed = true;
            onFail(downloadVideoTask);
          }
          if (!downloadVideoTask.isPaused && !downloadVideoTask.isFailed) {
            reload();
          }
        }
      }
    };

    // 都完成，读m3u8直接返回tsList
    // 只m3u8完成，读m3u8，下载key，成功后返回tsList
    if (indexHasDone) {
      await readIndex();
    } else {
      // 都没完成，下m3u8，读m3u8，下载key，成功后返回tsList
      await downloadIndex();
    }

    return tsItems;
  };

  // 找到下一个需要下载的ts文件
  const findLatestTs = (): TsItem | undefined => {
    const latestTs = tsList.find(
      (item): boolean =>
        !item.hasDone && !tsDownloadQueue.includes(item.tsId) && !item.isIgnore
    );
    return latestTs;
  };

  // 下载ts
  const downloadTs = async (ts: TsItem): Promise<void> => {
    if (
      downloadVideoTask.isPaused ||
      downloadVideoTask.isFailed ||
      ts.isIgnore
    ) {
      return;
    }
    const reload = downloadTs;

    const { tsId, tsName, folderName, serverDir } = ts;
    Log.i(
      DOWNLOAD_TAG,
      JSON.stringify({
        event: 'startDownloadTs',
        tsId,
        tsName,
        folderName
      })
    );

    const options = {
      fromUrl: `${serverDir}/${tsName}`,
      toFile: `${DOWNLOAD_PATH}/${folderName}/${tsName}`,
      background: true,
      discretionary: true,
      connectionTimeout: 8000,
      readTimeout: 8000,
      begin: (res: DownloadBeginCallbackResult): void => {
        const { jobId } = res;
        const index = tsList.findIndex((item): boolean => item.tsId === tsId);
        if (index !== -1) {
          tsList[index].jobId = jobId;
        }

        if (
          downloadVideoTask.isPaused ||
          downloadVideoTask.isFailed ||
          ts.isIgnore
        ) {
          RNFS.stopDownload(jobId);
        }
      }
    };

    const ret = await rnfsDownload(options);
    const [error, res] = await customAwait(ret.promise);

    if (error) {
      Log.i(
        DOWNLOAD_TAG,
        JSON.stringify({
          event: 'tsDownloadFailed',
          tsId,
          tsName,
          folderName,
          error
        })
      );
      failCount += 1;
      if (failCount === tsList.length * 2) {
        downloadVideoTask.isFailed = true;
        onFail(downloadVideoTask);
      }
      if (!downloadVideoTask.isPaused && !downloadVideoTask.isFailed) {
        reload(ts);
      }
      return;
    }

    if (res) {
      const { statusCode } = res;
      if (statusCode === 200) {
        Log.i(
          DOWNLOAD_TAG,
          JSON.stringify({
            event: 'tsDownloadSuccess',
            tsId,
            tsName,
            folderName,
            isPaused: downloadVideoTask.isPaused
          })
        );
        failCount = 0;
        const index = tsList.findIndex((item): boolean => item.tsId === tsId);
        if (index !== -1) {
          tsList[index].hasDone = true;
        }
        updateProgress();
        if (!downloadVideoTask.isPaused && !downloadVideoTask.isFailed) {
          const i = tsDownloadQueue.findIndex((item): boolean => item === tsId);
          if (i !== -1) {
            tsDownloadQueue.splice(i, 1);
          }
          if (downloadVideoTask.isPaused || downloadVideoTask.isFailed) {
            return;
          }
          const latestTs = findLatestTs();
          if (latestTs) {
            tsDownloadQueue.push(latestTs.tsId);
            downloadTs(latestTs);
          }
        }
      } else {
        Log.i(
          DOWNLOAD_TAG,
          JSON.stringify({
            event: 'tsDownloadFailed',
            tsId,
            tsName,
            folderName,
            res
          })
        );
        failCount += 1;
        if (failCount === tsList.length * 2) {
          downloadVideoTask.isFailed = true;
          onFail(downloadVideoTask);
        }
        if (!downloadVideoTask.isPaused && !downloadVideoTask.isFailed) {
          reload(ts);
        }
      }
    }
  };

  // 开始下载ts队列
  const startDownloadTsList = (): void => {
    while (tsDownloadQueue.length < MOST_TASK_NUM) {
      const latestTs = findLatestTs();
      if (downloadVideoTask.isPaused || downloadVideoTask.isFailed) {
        break;
      }
      if (latestTs) {
        tsDownloadQueue.push(latestTs.tsId);
        downloadTs(latestTs);
      } else {
        break;
      }
    }
  };

  // 开始下载
  const startAction = async (): Promise<void> => {
    downloadVideoTask.isPaused = false;
    downloadVideoTask.isFailed = false;
    downloadVideoTask.startTime =
      downloadVideoTask.startTime || moment().format('YYYY-MM-DD HH:mm:ss.SSS');
    onStart(downloadVideoTask);

    const tsLists: TsItem[][] = [];
    const { urls, type, parentId } = downloadVideoTask;

    // 若无m3u8则下载m3u8并读取ts列表，否则直接读取ts列表
    for (let i = 0; i < urls.length; i += 1) {
      const url = urls[i];

      const [serverDir] = url.split(`/${VIDEO_TYPE_DESC[type]}`);
      const dirArray = serverDir.split('/');
      const folderName = `${parentId}/${dirArray[dirArray.length - 1]}`;

      // 读取本地目录，判断m3u8和key是否下载完（主要用于上课时退出重进后，重置下载任务的判断）
      // eslint-disable-next-line no-await-in-loop
      const [readdirErr, currentFiles] = await customAwait(
        RNFS.readdir(`${DOWNLOAD_PATH}/${folderName}`)
      );
      if (currentFiles) {
        downloadVideoTask.indexHaveDone[i] = currentFiles.includes('hd.m3u8');
        downloadVideoTask.keyHaveDone[i] = currentFiles.includes('hd.key');
      }

      const { indexHaveDone, keyHaveDone } = downloadVideoTask;

      const indexHasDone = indexHaveDone[i];
      const keyHasDone = keyHaveDone[i];

      // 下载m3u8和key成功后返回ts队列
      // eslint-disable-next-line no-await-in-loop
      const [error, res] = await customAwait(
        downloadVideoIndex({
          url,
          index: i,
          indexHasDone,
          keyHasDone,
          folderName,
          serverDir
        })
      );
      if (error) {
        Log.i(
          DOWNLOAD_TAG,
          JSON.stringify({
            event: 'downloadVideoIndexFailed',
            folderName,
            error
          })
        );
        downloadVideoTask.isFailed = true;
        onFail(downloadVideoTask);
      }
      if (res) {
        tsLists[i] = res;
      }

      // 读取本地目录，设置已经下载完成的ts
      // eslint-disable-next-line no-await-in-loop
      if (currentFiles) {
        tsLists[i] = tsLists[i].map(
          (item): TsItem => ({
            ...item,
            hasDone: currentFiles.includes(item.tsName)
          })
        );
      }
    }

    // 将全部ts数组交叉排列
    tsList = sortBy(prop('tsName'))(flatten(tsLists));

    downloadVideoTask.tsDoneNum = tsList.reduce(
      (finishedNum, current): number => {
        const val = current.hasDone ? 1 : 0;
        return finishedNum + val;
      },
      0
    );

    // 开始下载ts
    // 首次开始时所有ts全部需要下载
    if (ignoreTsList.length === 0) {
      tsList = tsList.map(
        (item): TsItem => ({
          ...item,
          isIgnore: false
        })
      );
    } else {
      // 如果在这时期设置了ignoreTsList需要过滤，否则会全部下载
      tsList = tsList.map(
        (item): TsItem => ({
          ...item,
          isIgnore: !item.hasDone && getTsIsIgnored(item.tsName)
        })
      );
    }
    onTsListChanged(downloadVideoTask);
    startDownloadTsList();
    updateProgress();
  };

  // 暂停下载
  const pauseAction = (): void => {
    downloadVideoTask.isPaused = true;
    tsList.forEach((item): void => {
      if (!item.hasDone && item.jobId) {
        RNFS.stopDownload(item.jobId);
      }
    });
    onPause(downloadVideoTask);
  };

  // 删除下载任务
  const deleteAction = (): void => {
    pauseAction();
    const { urls, parentId, type } = downloadVideoTask;
    // 删除文件
    urls.forEach((url): void => {
      const [serverDir] = url.split(`/${VIDEO_TYPE_DESC[type]}`);
      const dirArray = serverDir.split('/');
      const folderName = `${parentId}/${dirArray[dirArray.length - 1]}`;
      RNFS.unlink(`${DOWNLOAD_PATH}/${folderName}`);
    });
    onDelete(downloadVideoTask);
  };

  const startDownloadAllTs = (): void => {
    tsList.forEach((item): void => {
      if (
        !item.hasDone &&
        !tsDownloadQueue.includes(item.tsId) &&
        !item.isIgnore
      ) {
        tsDownloadQueue.push(item.tsId);
        downloadTs(item);
      }
    });
    Log.i(
      DOWNLOAD_TAG,
      JSON.stringify({
        event: 'startDownloadAllTs',
        tsDownloadQueue
      })
    );
  };

  const stopDownloadAllTs = (): void => {
    // 只移除根本没有开始下载的ts，已经开始下载的无法移除，否则会导致重复下载
    tsList.forEach((item): void => {
      if (!item.jobId) {
        const index = tsDownloadQueue.findIndex(ts => item.tsId === ts);
        if (index > -1) {
          tsDownloadQueue.splice(0, 1);
        }
      }
    });
  };

  const setIgnoreTsList = (value: string[]): void => {
    ignoreTsList = [...value];
    tsList = tsList.map(
      (item): TsItem => ({
        ...item,
        isIgnore: !item.hasDone && getTsIsIgnored(item.tsName)
      })
    );
    onTsListChanged(downloadVideoTask);
  };

  return {
    ...downloadVideoTask,
    start: startAction,
    pause: pauseAction,
    delete: deleteAction,
    startDownloadAllTs,
    stopDownloadAllTs,
    setIgnoreTsList
  };
};

export default {
  initDownloadVideoTask
};
